#include <tuple>
#include <typeinfo>
#include <vector>
#include <numeric>

#include "worker.hh"
#include "../kernel/kernels.hh"
#include "../utils/debug.hh"

namespace icesp
{
    namespace worker_impl
    {
        template <class U>
        void run(
                std::string const& name,
                units::run_times const& ntimes,
                units::thread_count const& nthreads,
                units::relax_count_per_iter const& nrelax,
                graph::adj_matrix & mat,
                units::graph_source_point const& source,
                units::graph_target_point const& target,
                painter& p,
                painter& pad)
        {

            // TODO c++ demangle, e.g. c++filt <symbol>
            // DEBUG("running case" + name + " via method: " << typeid(U).name() << ".");
            // std::cout << "running case " + name + " via method: "
            //             << U::name << ".\n";
            PRINT("\n----------------------------------------------------------------\n");
            PRINT2("running case: ", name);
            PRINT3(", via method: ", U::name, "\n");

            U::init(nthreads.value, nrelax.value);
            std::vector<double> result;
            for (auto i = 0; i < ntimes.value; i++) {
                if (U::is_pad) {
                    auto t = U::compute(mat, pad, mat.new_node_map[source.value],
                                            target.value);
                    result.push_back(t);
                    PRINT3(i << "th test time: ", t, ".\n");
                } else {
                    auto t = U::compute(mat, p, source.value, target.value);
                    result.push_back(t);
                    PRINT3(i << "th test time: ", t, ".\n");
                }
            }
            auto avg = std::accumulate(result.begin(), result.end(), 0.0) / result.size();
            PRINT3("-> average time: ", avg, ".\n");
        }

        template <class U, class T, class ...TS>
        void run(
                std::string const& name,
                units::run_times const& ntimes,
                units::thread_count const& nthreads,
                units::relax_count_per_iter const& nrelax,
                graph::adj_matrix & mat,
                units::graph_source_point const& source,
                units::graph_target_point const& target,
                painter& p,
                painter& pad)
        {
            run       <U>(name, ntimes, nthreads, nrelax, mat, source, target, p, pad);
            run<T, TS...>(name, ntimes, nthreads, nrelax, mat, source, target, p, pad);
        }

        template <class T, class ...TS>
        void run(
                std::string const& name,
                units::run_times const& ntimes,
                units::thread_count const& nthreads,
                units::relax_count_per_iter const& nrelax,
                graph::adj_matrix & mat,
                units::graph_source_point const& source,
                units::graph_target_point const& target,
                painter& p,
                painter& pad,
                std::tuple<T, TS...>)
        {
            run<T, TS...>(name, ntimes, nthreads, nrelax, mat, source, target, p, pad);
        }
    }

    void worker::run(
            std::string const& name,
            units::run_times const& ntimes,
            units::thread_count const& nthreads,
            units::relax_count_per_iter const& nrelax,
            graph::adj_matrix & mat,
            units::graph_source_point const& source,
            units::graph_target_point const& target,
            painter& p,
            painter& pad)
    {
        worker_impl::run(
            name,
            ntimes,
            nthreads,
            nrelax,
            mat,
            source,
            target,
            p,
            pad,
            all_run_kernals{});
    }
}

